package ru.amse.stroganova.ui.visual;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ru.amse.stroganova.graph.Edge;
import ru.amse.stroganova.graph.Vertex;
import ru.amse.stroganova.presentation.EdgePresentation;
import ru.amse.stroganova.presentation.GraphPresentation;
import ru.amse.stroganova.presentation.VertexPresentation;

/**
 * This class represents a tool for marking and unmarking graph presentation elements.
 * 
 * @author Dasha Stroganova
 *
 */
public class GraphElementMarker {

	private static final int VERTEX_MARKING_SHIFT = 3;
	
	private static final int EDGE_MARKING_SHIFT = 7;
	
	private static final Color ERROR_COLOR = Color.RED;
	
	private static final Color MARK_COLOR = new Color(10, 220, 20);
	
	private GraphPresentation graphPresentation;
	
	private final Set<EdgePresentation> markedEdges;
	
	private final Map<Edge, Integer> edgesData;
	
	private final Map<Vertex, Integer> verticesData;
	
	private final List<VisualStateListener> listeners;
	
	private final Set<VertexPresentation> markedVertices;
	
	/**
	 * Creates new marker for given presentation.
	 * 
	 * @param graphPresentation
	 */
	public GraphElementMarker(GraphPresentation graphPresentation) {
		this.graphPresentation = graphPresentation;
		markedEdges = new HashSet<EdgePresentation>();
		verticesData = new HashMap<Vertex, Integer>();
		edgesData = new HashMap<Edge, Integer>();
		listeners = new ArrayList<VisualStateListener>();
		markedVertices = new HashSet<VertexPresentation>();
	}
	
	/**
	 * Adds the specified VisualStateListaner to recive notification about state
	 * change.
	 * 
	 * @param listener
	 */
	public void addVisualStateListener(VisualStateListener listener) {
		listeners.add(listener);
	}
	
	/**
	 * Removes the specified VisualStateListaner from this presentation.
	 * 
	 * @param listener
	 */
	public void removeVisualStateListener(VisualStateListener listener) {
		listeners.remove(listener);
	}
	
	/**
	 * Sets new graph presentation.
	 * 
	 * @param graphPresentation
	 */
	public void setGraphPresentation(GraphPresentation graphPresentation) {
		this.graphPresentation = graphPresentation;
		unmark();
	}
	
	private void fireVisualStateChange() {
		for (VisualStateListener listener : listeners) {
			listener.visualStateChanged();
		}
	}
	
	private void fireVisualChandesCancelled() {
		for (VisualStateListener listener : listeners) {
			listener.visualChangesCancelled();
		}
	}
	
	/**
	 * Marks presentations of given edges.
	 * 
	 * @param edgesToMark
	 * @param isError 
	 */
	public void markEdges(Collection<Edge> edgesToMark, boolean isError) {
		for (Edge edge : edgesToMark) {
			graphPresentation.getPresentationForEdge(edge).mark(isError ? ERROR_COLOR : MARK_COLOR);
			markedEdges.add(graphPresentation.getPresentationForEdge(edge));
		}
		fireVisualStateChange();
	}
	
	/**
	 * Marks graph vertices with given data.
	 * 
	 * @param data
	 */
	//TODO Eliminate this method. Replace its call with cycle
	public void markVerticesWithData(Map<Vertex, Integer> data) {
		verticesData.putAll(data);
		fireVisualStateChange();
	}
	
	/**
	 * Marks graph vertex with given data.
	 * @param vertex 
	 * 
	 * @param data
	 */
	public void markVertexWithData(Vertex vertex, Integer data) {
		verticesData.put(vertex, data);
		fireVisualStateChange();
	}
	
	/**
	 * Marks graph edges with given data.
	 * 
	 * @param data
	 */
	//TODO if possible make one method for vertex marking and one for edge marking
	public void markEdgesWithData(Map<Edge, Integer> data) {
		edgesData.putAll(data);
		fireVisualStateChange();
	}
	
	/**
	 * Markes selected vertex.
	 * 
	 * @param vp
	 */
	public void markVertex(VertexPresentation vp) {
		markedVertices.add(vp);
		fireVisualStateChange();
	}
	
	/**
	 * Markes given vertices.
	 * @param verticesToMark 
	 * 
	 */
	public void markVertices(Collection<Vertex> verticesToMark) {
		for (Vertex vertex : verticesToMark) {
			markedVertices.add(graphPresentation.getPresentationForVertex(vertex));			
		}
		fireVisualStateChange();
	}
	
	/**
	 * Unmarks previously marked elements.
	 * 
	 */
	public void unmark() {
		for (Iterator<EdgePresentation> it = markedEdges.iterator(); it.hasNext();) {
			EdgePresentation ep = it.next();
			ep.unmark();
			it.remove();
		}
		verticesData.clear();
		edgesData.clear();
		markedVertices.clear();
		fireVisualChandesCancelled();
	}
	
	/**
	 * Unmarks previously marked edges.
	 * 
	 */
	public void unmarkEdges() {
		for (Iterator<EdgePresentation> it = markedEdges.iterator(); it.hasNext();) {
			EdgePresentation ep = it.next();
			ep.unmark();
			it.remove();
		}
		//if ((verticesData.size() == 0) && (markedVertices.size() == 0)) {
		//	fireVisualChandesCancelled();
		//} else {
			fireVisualStateChange();
		//}
	}
	
	/**
	 * Unmarks previously marked vertices.
	 * 
	 */
	public void unmarkVertices() {
		markedVertices.clear();
		fireVisualStateChange();
	}
	
	/**
	 * Performs painting required for marking elements.
	 * 
	 * @param gr graphics
	 * @param backgroundColor 
	 */
	public void paint(Graphics gr, Color backgroundColor) {
		gr.setColor(new Color(23, 15, 181));
		for (VertexPresentation vp : markedVertices) {
			Point center = vp.getCenter();
			gr.drawOval(center.x - VertexPresentation.RADIX - VERTEX_MARKING_SHIFT, center.y - VertexPresentation.RADIX - VERTEX_MARKING_SHIFT,
					2 * (VertexPresentation.RADIX + VERTEX_MARKING_SHIFT), 2 * (VertexPresentation.RADIX + VERTEX_MARKING_SHIFT));
		}
		for (Vertex vertex : verticesData.keySet()) {
			if (verticesData.get(vertex) == Integer.MAX_VALUE) {
				drawInfinitySymbol(gr, graphPresentation.getPresentationForVertex(vertex));
			} else {
				VertexPresentation vp = graphPresentation.getPresentationForVertex(vertex);
				int x = gr.getFontMetrics().stringWidth(verticesData.get(vertex).toString()) / 2;
				int y = (gr.getFontMetrics().getMaxAscent() - gr.getFontMetrics().getMaxDescent()) / 2;		
				gr.drawString(verticesData.get(vertex).toString(), vp.getCenter().x - x, vp.getCenter().y + y);
			}
		}
		for (Edge edge : edgesData.keySet()) {
			drawEdgeData(gr, graphPresentation.getPresentationForEdge(edge), backgroundColor);
		}
	}
	
	private void drawInfinitySymbol(Graphics gr, VertexPresentation vp) {
		int shift = 3;
		gr.drawOval(vp.getCenter().x - VertexPresentation.RADIX + shift, vp.getCenter().y - VertexPresentation.RADIX / shift, VertexPresentation.RADIX - shift, 2 * VertexPresentation.RADIX / shift);
		gr.drawOval(vp.getCenter().x, vp.getCenter().y - VertexPresentation.RADIX / shift, VertexPresentation.RADIX - shift, 2 * VertexPresentation.RADIX / shift);
	}
	
	private void drawEdgeData(Graphics gr, EdgePresentation ep, Color backgroundColor) {
		String dataToDraw = edgesData.get(ep.getEdge()) + "/";
		int width = gr.getFontMetrics().stringWidth(dataToDraw);
		int height = gr.getFontMetrics().getMaxAscent() - gr.getFontMetrics().getMaxDescent();
		int x = ep.getUpperLeftWeightPoint().x - width - EDGE_MARKING_SHIFT;
		int y = ep.getUpperLeftWeightPoint().y + height + EdgePresentation.WEIGHT_SHIFT / 2;
		gr.setColor(backgroundColor);
		gr.fillRect(x - EdgePresentation.WEIGHT_SHIFT / 2, y - EdgePresentation.WEIGHT_SHIFT - height + 1, width + EdgePresentation.WEIGHT_SHIFT / 2, height + EdgePresentation.WEIGHT_SHIFT);
		gr.setColor(MARK_COLOR);
		gr.drawString(dataToDraw, x, y);
	}
}
